package top.riske.gen3d.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.tencentcloudapi.ai3d.v20250513.models.*;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import top.riske.gen3d.DTO.HunyuanInputMultiViewImagesDto;
import top.riske.gen3d.DTO.HunyuanInputParamDto;
import top.riske.gen3d.enums.HunyuanModelNameEnum;
import top.riske.gen3d.http.exception.BisException;
import top.riske.gen3d.http.response.Hunyuan3DJobResponse;
import top.riske.gen3d.service.Hunyuan3DFileService;
import top.riske.gen3d.service.Hunyuan3DJobService;
import top.riske.gen3d.entity.Hunyuan3DFile;
import top.riske.gen3d.entity.Hunyuan3DJob;
import top.riske.gen3d.service.Hunyuan3DService;
import top.riske.gen3d.util.FileDownloadUtils;
import top.riske.gen3d.util.FileUtils;
import top.riske.gen3d.util.Tencent3DUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class Hunyuan3DServiceImpl implements Hunyuan3DService {

    /**
     * 腾讯3D密钥方法,从配置文件中获取，或者从环境变量中读取
     */
    @Value("${gen3d.hunyuan.secret.method}")
    private String hunyuanSecretMethod;

    /**
     * 腾讯3D密钥ID
     */
    @Value("${gen3d.hunyuan.secret.id}")
    private String hunyuanSecretId;

    /**
     * 腾讯3D密钥
     */
    @Value("${gen3d.hunyuan.secret.key}")
    private String hunyuanSecretKey;

    /**
     * 腾讯3D地域
     */
    @Value("${gen3d.hunyuan.region}")
    private String hunyuanRegion;

    /**
     * 文件保存路径
     */
    @Value("${gen3d.file.path}")
    private String gen3DFilePath;

    /**
     * 模型文件从腾讯云下载的文件类型,normal,rapid
     */
    @Value("${gen3d.hunyuan.model.file-type}")
    private String gen3DModelFileZipType;

    /**
     * 模型文件从腾讯云下载的文件类型,pro
     */
    @Value("${gen3d.hunyuan.model.file.pro.type}")
    private String gen3DModelFileProType;

    /**
     * 模型文件预览图片类型
     */
    @Value("${gen3d.hunyuan.model.file-preview-file-type}")
    private String gen3DModelPreviewImageType;

    @Resource
    private Hunyuan3DFileService hunyuan3DFileService;

    @Resource
    private Hunyuan3DJobService hunyuan3DJobService;



    @Override
    public Hunyuan3DJob submitJobForText(HunyuanInputParamDto inputParam, Long accountId) throws TencentCloudSDKException {
        // 获取腾讯3D密钥id，密钥
        String[] secret = getSecret();
        // 创建 Hunyuan3DJob 任务对象
        Hunyuan3DJob job;
        if(inputParam.getModelName().equals(HunyuanModelNameEnum.Normal.getName())){
            // 提交任务
            SubmitHunyuanTo3DJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByTextForNormal(secret[0], secret[1], hunyuanRegion, inputParam.getText());
            job = new Hunyuan3DJob(res.getJobId(),res.getRequestId(),inputParam);
        }else if (inputParam.getModelName().equals(HunyuanModelNameEnum.Pro.getName())){
            SubmitHunyuanTo3DProJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByTextForPro(secret[0], secret[1], hunyuanRegion, inputParam.getText(), inputParam.getEnablePBR(), inputParam.getFaceCount(), inputParam.getGenerateType());
            job = new Hunyuan3DJob(res.getJobId(),res.getRequestId(), inputParam);
        }else if (inputParam.getModelName().equals(HunyuanModelNameEnum.Rapid.getName())){
            SubmitHunyuanTo3DRapidJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByTextForRapid(secret[0], secret[1], hunyuanRegion, inputParam.getText());
            job = new Hunyuan3DJob(res.getJobId(),res.getRequestId(), inputParam);
        }else {
            throw new BisException("未知的模型名称："+inputParam.getModelName());
        }
        job.setAccountId(accountId);
        log.info("文生3D提交任务成功：{}" , job);
        // 写入数据库
        hunyuan3DJobService.save(job);
        return job;
    }

    @Override
    public Hunyuan3DJob submitJobForImage(HunyuanInputParamDto inputParam, Long accountId) throws TencentCloudSDKException {
        // 获取腾讯3D密钥id，密钥
        String[] secret = getSecret();
        Hunyuan3DJob job;
        if(inputParam.getModelName().equals(HunyuanModelNameEnum.Normal.getName())){
            // 提交任务
            SubmitHunyuanTo3DJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByImageForNormal(secret[0], secret[1], hunyuanRegion, inputParam.getImages());
            job = new Hunyuan3DJob(res.getJobId(),res.getRequestId(),inputParam);
        }else if (inputParam.getModelName().equals(HunyuanModelNameEnum.Pro.getName())){
            SubmitHunyuanTo3DProJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByImageForPro(secret[0], secret[1], hunyuanRegion, inputParam.getImages(), inputParam.getEnablePBR(), inputParam.getFaceCount(), inputParam.getGenerateType());
            job = new Hunyuan3DJob(res.getJobId(),res.getRequestId(), inputParam);
        }else if (inputParam.getModelName().equals(HunyuanModelNameEnum.Rapid.getName())){
            SubmitHunyuanTo3DRapidJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByImageForRapid(secret[0], secret[1], hunyuanRegion, inputParam.getImages());
            job = new Hunyuan3DJob(res.getJobId(),res.getRequestId(), inputParam);
        }else {
            throw new BisException("未知的模型名称："+inputParam.getModelName());
        }
        job.setAccountId(accountId);
        job.setImageGen(true);
        // 把输入图片信息转换为JSON存储
        job.setText(JSONUtil.toJsonStr(inputParam.getImages()));
        log.info("图生3D提交任务成功：{}" , job);
        // 写入数据库
        hunyuan3DJobService.save(job);
        return job;
    }

    @Override
    public Hunyuan3DJob queryJob(Long id, Long accountId, HttpServletRequest request) throws TencentCloudSDKException {
        // 查询任务
        Hunyuan3DJob job = hunyuan3DJobService.getById(id);
        if (job == null){
            throw new BisException("任务不存在");
        }
        if(!job.getAccountId().equals(accountId)){
            throw new BisException("任务不属于当前用户");
        }

        // 任务已经完全成功的状态
        if(job.getStatus() == 5){
            // 构造查询文件查询条件
            LambdaQueryWrapper<Hunyuan3DFile> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Hunyuan3DFile::getJobId,job.getJobId());
            // 查询文件信息
            List<Hunyuan3DFile> files = hunyuan3DFileService.list(queryWrapper);
            job.setResultFile3Ds(files);
            return job;
        }

        // 获取任务状态
        File3D[] file3DArray = this.updateJobStatus(job);
        if(file3DArray == null){
            // 前后的状态未更新，直接返回
            return job;
        }

        // 任务完成的情况，更新文件信息
        if(job.getStatus() == 3){
            log.info("任务:{} 完成，开始下载文件",job.getJobId());
            // 下载文件到本地,并保存文件信息到数据库
            List<Hunyuan3DFile> files;
            try {
                // 下载文件
                files = this.parseTencentFileAndSaveToLocal(file3DArray,job.getJobId());
            } catch (IOException e) {
                log.error("保存文件到本地失败：{}" , e.getMessage());
                // 更新任务状态
                job.setStatus(4);
                hunyuan3DJobService.updateById(job);
                throw new BisException("保存文件到本地失败，请稍后重试");
            }
            job.setResultFile3Ds(files);
            // 任务完全成功，更新任务状态
            job.setStatus(5);
            hunyuan3DJobService.updateById(job);
        }
        // 处理图片链接URL
        if(job.getResultFile3Ds()!= null){
            for (Hunyuan3DFile file : job.getResultFile3Ds()){
                hunyuan3DJobService.handlePathToUrl(file, request);
            }
        }

        return job;
    }

    @Override
    public Hunyuan3DJob submitJobForRetry(Long id, Long accountId) throws TencentCloudSDKException{
        Hunyuan3DJob job = hunyuan3DJobService.getById(id);
        job.setStatus(0);
        if(job.getImageGen()){
            return this.submitJobForRetryByImage(job, accountId);
        }else{
            return this.submitJobForRetryByText(job, accountId);
        }
    }

    /**
     * 解析腾讯返回的文件信息，下载文件，预览图文件，并保存到本地
     * @param file3Ds
     * @param jobId
     * @return
     * @throws IOException
     */
    private List<Hunyuan3DFile> parseTencentFileAndSaveToLocal(File3D[] file3Ds, String jobId) throws IOException {
        List<Hunyuan3DFile> files = new ArrayList<>();
        for(File3D file3D : file3Ds){
            if(file3D.getType().equalsIgnoreCase("obj")){
                files.add(this.parseTencentFileAndSaveToLocalForObj(file3D,jobId));
            }else if (file3D.getType().equalsIgnoreCase("glb")){
                files.add(this.parseTencentFileAndSaveToLocalForGlb(file3D,jobId));
            }
        }
        return files;
    }


    /**
     * 解析腾讯返回的文件信息，下载OBJ文件，预览图文件，并保存到本地
     * @param resultFile3D
     * @return
     */
    private Hunyuan3DFile parseTencentFileAndSaveToLocalForObj(File3D resultFile3D, String jobId) throws IOException {
        // 创建文件信息
        Hunyuan3DFile file = new Hunyuan3DFile();
        file.setJobId(jobId);
        file.setType(resultFile3D.getType());
        file.setTencentFileUrl(resultFile3D.getUrl());
        file.setTencentPreviewImageUrl(resultFile3D.getPreviewImageUrl());
        // 下载zip文件到本地，以及预览图片到本地
        String localZipFilePath = gen3DFilePath + FileUtils.genFilePathByTime(IdUtil.simpleUUID())+gen3DModelFileZipType;
        String localPreviewImagePath = gen3DFilePath + FileUtils.genFilePathByTime(IdUtil.simpleUUID())+gen3DModelPreviewImageType;
        // 下载zip文件到本地
        FileDownloadUtils.downloadFile(resultFile3D.getUrl(), localZipFilePath);
        // 保存zip文件信息
        file.setLocalZipFilePath(FileUtils.getRelativePath(localZipFilePath,gen3DFilePath));
        log.info("下载zip文件到本地成功：{}" , localZipFilePath);
        // 下载预览图片到本地
        FileDownloadUtils.downloadFile(resultFile3D.getPreviewImageUrl(), localPreviewImagePath);
        log.info("下载预览图片到本地成功：{}" , localPreviewImagePath);
        // 保存预览图片信息
        file.setLocalPreviewImagePath(FileUtils.getRelativePath(localPreviewImagePath,gen3DFilePath));
        // 解压zip文件
        String zipFilePath = FileUtils.unZipFile(localZipFilePath);
        log.info("解压zip文件成功：{}" , zipFilePath);
        // 获取所有文件
        List<String> allFiles = FileUtils.getAllFilesInDirectory(zipFilePath);
        // 遍历所有文件,读取模型文件obj，材料文件mtl，材料预览文件png
        for (String path : allFiles) {
            // 移除文件前缀
            path = FileUtils.getRelativePath(path, gen3DFilePath);
            if(path.endsWith(".obj")){
                file.setLocalFilePath(path);
            }else if (path.endsWith(".mtl")){
                file.setLocalMaterialPath(path);
            }else if (path.endsWith(".png")){
                file.setLocalMaterialImagePath(path);
            }else {
                log.warn("解压zip后得到未知文件：{}",path);
            }
        }
        // 保存文件信息到数据库
        hunyuan3DFileService.save(file);
        log.info("保存文件信息成功：{}",file);
        return file;
    }

    /**
     * 解析腾讯返回的文件信息，下载GLB文件，预览图文件，并保存到本地
     * @param resultFile3D 文件信息
     * @return
     */
    private Hunyuan3DFile parseTencentFileAndSaveToLocalForGlb(File3D resultFile3D, String jobId) throws IOException {
        // 创建文件
        Hunyuan3DFile file = new Hunyuan3DFile();
        file.setJobId(jobId);
        file.setType(resultFile3D.getType());
        file.setTencentFileUrl(resultFile3D.getUrl());
        file.setTencentPreviewImageUrl(resultFile3D.getPreviewImageUrl());
        // 下载GLB文件到本地，pro模型下，直接是glb文件，不是zip文件
        String localGlbFilePath = gen3DFilePath + FileUtils.genFilePathByTime(IdUtil.simpleUUID())+gen3DModelFileProType;
        FileDownloadUtils.downloadFile(resultFile3D.getUrl(), localGlbFilePath);
        file.setLocalFilePath(FileUtils.getRelativePath(localGlbFilePath,gen3DFilePath));
        // 下载预览图片到本地
        String localPreviewImagePath = gen3DFilePath + FileUtils.genFilePathByTime(IdUtil.simpleUUID())+gen3DModelPreviewImageType;
        FileDownloadUtils.downloadFile(resultFile3D.getPreviewImageUrl(), localPreviewImagePath);
        log.info("下载glb预览图片到本地成功：{}" , localPreviewImagePath);
        // 保存预览图片信息
        file.setLocalPreviewImagePath(FileUtils.getRelativePath(localPreviewImagePath,gen3DFilePath));
        // 保存文件信息到数据库
        hunyuan3DFileService.save(file);
        log.info("保存glb文件信息成功：{}",file);
        return file;
    }

    /**
     * 获取腾讯3D密钥,从配置文件中获取，或者从环境变量中读取
     * @return 腾讯3D密钥[id,secret]
     */
    private String[] getSecret(){
        return switch (hunyuanSecretMethod) {
            case "env" -> new String[]{System.getenv("GEN_HUNYUAN_SECRET_ID"), System.getenv("GEN_HUNYUAN_SECRET_KEY")};
            case "config" -> new String[]{hunyuanSecretId, hunyuanSecretKey};
            default -> new String[]{hunyuanSecretId, hunyuanSecretKey};
        };
    }


    /**
     * 检查更新任务状态,得到生成的文件信息
     * @param job 任务
     * @return 为null则状态未更新，否则返回文件信息
     */
    private File3D[] updateJobStatus(Hunyuan3DJob job) throws TencentCloudSDKException {
        // 获取腾讯3D密钥id，密钥
        String[] secret = getSecret();
        // 连接腾讯混元，查询任务
        File3D[] file3DArray;
        // 判断当前是什么任务
        if(job.getModelName().equals(HunyuanModelNameEnum.Normal.getName())){
            QueryHunyuanTo3DJobResponse res = Tencent3DUtils.queryHunyuanTo3DJobForNormal(secret[0], secret[1], hunyuanRegion, job.getJobId());
            file3DArray = res.getResultFile3Ds();
            // 判断任务状态是否有更新，如果没有更新，则直接返回
            if (!setJobFromTencentNewStatus(job, res.getStatus(), res.getRequestId(), res.getErrorCode(), res.getErrorMessage())){
                return null;
            }
        }else if(job.getModelName().equals(HunyuanModelNameEnum.Pro.getName())){
            QueryHunyuanTo3DProJobResponse res = Tencent3DUtils.queryHunyuanTo3DJobForPro(secret[0], secret[1], hunyuanRegion, job.getJobId());
            file3DArray = res.getResultFile3Ds();
            // 判断任务状态是否有更新，如果没有更新，则直接返回
            if (!setJobFromTencentNewStatus(job, res.getStatus(), res.getRequestId(), res.getErrorCode(), res.getErrorMessage())){
                return null;
            }
        }else if (job.getModelName().equals(HunyuanModelNameEnum.Rapid.getName())){
            QueryHunyuanTo3DRapidJobResponse res = Tencent3DUtils.queryHunyuanTo3DJobForRapid(secret[0], secret[1], hunyuanRegion, job.getJobId());
            file3DArray = res.getResultFile3Ds();
            // 判断任务状态是否有更新，如果没有更新，则直接返回
            if (!setJobFromTencentNewStatus(job, res.getStatus(), res.getRequestId(), res.getErrorCode(), res.getErrorMessage())){
                return null;
            }
        }else{
            log.error("未知的模型名称：{}" , job.getModelName());
            throw new BisException("未知的模型名称："+job.getModelName());
        }
        // 任务执行查询成功，更新任务状态
        log.info("查询任务成功，任务状态更新：{}" , job);
        hunyuan3DJobService.updateById(job);
        return file3DArray;
    }

    /**
     * 设置任务状态
     * @param job 任务
     * @param status 状态
     * @param requestId 请求id
     * @param errorCode 错误码
     * @param errorMessage 错误信息
     * @return 状态是否更新了
     */
    private boolean setJobFromTencentNewStatus(Hunyuan3DJob job, String status, String requestId, String errorCode, String errorMessage) {
        if(job.getStatus() == Tencent3DUtils.getJobStatus(status)){
            log.info("任务状态没有更新：{}" , job);
            return false;
        }
        job.setRequestId(requestId);
        job.setStatus(Tencent3DUtils.getJobStatus(status));
        job.setErrorCode(errorCode);
        job.setErrorMessage(errorMessage);
        job.setQueryCount(job.getQueryCount() + 1);
        return true;
    }

    /**
     * 重新提交任务,文生3D
     * @param job
     * @param accountId
     * @return
     */
    private Hunyuan3DJob submitJobForRetryByText(Hunyuan3DJob job, Long accountId) throws TencentCloudSDKException {
        // 获取腾讯3D密钥id，密钥
        String[] secret = getSecret();
        if(job.getModelName().equals(HunyuanModelNameEnum.Normal.getName())){
            // 提交任务
            SubmitHunyuanTo3DJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByTextForNormal(secret[0], secret[1], hunyuanRegion, job.getText());
            job.setRequestId(res.getRequestId());
            job.setJobId(res.getJobId());
        }else if (job.getModelName().equals(HunyuanModelNameEnum.Pro.getName())){
            SubmitHunyuanTo3DProJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByTextForPro(secret[0], secret[1], hunyuanRegion, job.getText(), job.getEnablePBR(), job.getFaceCount(), job.getGenerateType());
            job.setRequestId(res.getRequestId());
            job.setJobId(res.getJobId());
        }else if (job.getModelName().equals(HunyuanModelNameEnum.Rapid.getName())){
            SubmitHunyuanTo3DRapidJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByTextForRapid(secret[0], secret[1], hunyuanRegion, job.getText());
            job.setRequestId(res.getRequestId());
            job.setJobId(res.getJobId());
        }else {
            throw new BisException("未知的模型名称："+job.getModelName());
        }
        job.setAccountId(accountId);
        log.info("文生3D提交重试任务成功：{}" , job);
        // 写入数据库
        hunyuan3DJobService.updateById(job);
        return job;
    }

    /**
     * 重新提交任务,图生3D
     * @param job
     * @param accountId
     * @return
     */
    private Hunyuan3DJob submitJobForRetryByImage(Hunyuan3DJob job, Long accountId) throws TencentCloudSDKException {
        // 解析json
        JSONArray inputParamList = JSONUtil.parseArray(job.getText());
        List<HunyuanInputMultiViewImagesDto> imageList = inputParamList.stream().map(item -> JSONUtil.toBean(item.toString(), HunyuanInputMultiViewImagesDto.class)).toList();
        // 获取腾讯3D密钥id，密钥
        String[] secret = getSecret();
        if(job.getModelName().equals(HunyuanModelNameEnum.Normal.getName())){
            // 提交任务
            SubmitHunyuanTo3DJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByImageForNormal(secret[0], secret[1], hunyuanRegion, imageList);
            job.setRequestId(res.getRequestId());
            job.setJobId(res.getJobId());
        }else if (job.getModelName().equals(HunyuanModelNameEnum.Pro.getName())){
            SubmitHunyuanTo3DProJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByImageForPro(secret[0], secret[1], hunyuanRegion, imageList, job.getEnablePBR(), job.getFaceCount(), job.getGenerateType());
            job.setRequestId(res.getRequestId());
            job.setJobId(res.getJobId());
        }else if (job.getModelName().equals(HunyuanModelNameEnum.Rapid.getName())){
            SubmitHunyuanTo3DRapidJobResponse res = Tencent3DUtils.submitHunyuanTo3DJobByImageForRapid(secret[0], secret[1], hunyuanRegion, imageList);
            job.setRequestId(res.getRequestId());
            job.setJobId(res.getJobId());
        }else {
            throw new BisException("未知的模型名称："+job.getModelName());
        }
        log.info("图生3D提交重试任务成功：{}" , job);
        // 写入数据库
        hunyuan3DJobService.updateById(job);
        return job;
    }

}
